iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
Modern Web

轉生成前端工程師後,30步離開新手村!系列 第 19

# 前端實作案例分享:  隨著頁面改變樣式的NavBar

  • 分享至 

  • xImage
  •  

提問:專案需要加入一個 NavBar,依據不同頁面顯示切換樣式。


實作方式:

這次的架構針對 NavBar 的部分總共會有四個角色:NavBar 本體控制 NavBar 的 service管理 NavBar 狀態的 Store,以及處理 NavBar 與外部排版關係的自訂 directive

設計理念如下:
https://ithelp.ithome.com.tw/upload/images/20241003/20169487GjqWecgYmA.png

首先在進入 Angular 應用時,訂閱路由導引;在路由改變時,呼叫 NavBar Service 的 method 判斷該路由應使用的樣式,並將狀態更新到 store 中。會使用 NavBar 狀態的對象有兩個:一是 NavBar 本身,二是自定義 directivedirective 會依據狀態回傳 cssclass name,讓外部元件可以透過 class name 來搭配 NavBar 進行 UI 調整,有點類似 side effect 的處理方式。

我們來看看程式碼。

首先是 NavBar 本身,其實非常簡單,唯二要做的就是訂閱狀態以及依據狀態切換 UI 呈現。Html 的部分就不特別討論了,ng-if 就可以解決。

@Component({
  selector: 'app-self-control-nav',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './self-control-nav.component.html',
  styleUrls: ['./self-control-nav.component.scss'],
})
export class SelfControlNavComponent implements OnDestroy {
  private store = inject(Store);
  private destroy$ = new Subject<void>();

  protected navControl$ = this.store
    .select(selectLayoutNavControl)
    .pipe(takeUntil(this.destroy$));

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

接著我們將 NavBar importAppComponent。因為我希望 NavBar 的生命週期與應用相同,並且是在 DOM 的最外層。

<app-self-control-nav></app-self-control-nav>
<router-outlet />

再來,AppComponent 要負責的是訂閱路由,以實作依據路由切換 NavBar 樣式的功能。

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, SelfControlNavComponent],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  private router = inject(Router);
  private controlNavService = inject(ControlNavService);

  ngOnInit(): void {
    this.router.events
      .pipe(
        filter(
          (event): event is NavigationEnd => event instanceof NavigationEnd
        )
      )
      .subscribe((res) => {
        const _url = res.url.split('?')[0];
        this.controlNavService.routerSetNavBar(_url);
      });
  }
}

因為我們把路由和 NavBar 樣式的關聯封裝在 NavBarService 中,所以呼叫 Service 的方法,把 url 傳入即可。

下一步前往 NavBar Service,它提供兩個方法:一是剛剛提到的透過路由切換的方法,二是提供直接傳入要切換的樣式的方法。

@Injectable({
  providedIn: 'root',
})
export class ControlNavService {
  private store = inject(Store);

  routerSetNavBar(url: string): void {
    switch (url) {
      case 'route/a':
        this.manualSetNavBar(SelfControlNavType.None, '');
        break; // 加上 break 避免 fall-through
      default:
        this.manualSetNavBar(SelfControlNavType.Sample, 'Sample');
        break;
    }
  }

  manualSetNavBar(selfNavType: SelfControlNavType, selfNavTitle: string): void {
    this.store.dispatch(
      LayoutActions.uPDATE_NAV_CONTROL({
        navControl: { selfControlNavType: selfNavType, title: selfNavTitle },
      })
    );
  }
}

最後是另一個會用到 NavBar 狀態的 directive

@Directive({
  selector: '[appSelfControlNavCss]',
  standalone: true,
})
export class SelfControlNavCssDirective implements OnInit, OnDestroy {
  private renderer = inject(Renderer2);
  private elementRef = inject(ElementRef);
  private store = inject(Store);``
  private destroy$ = new Subject<void>();

  ngOnInit(): void {
    this.store
      .select(selectLayoutNavControl)
      .pipe(takeUntil(this.destroy$))
      .subscribe((res) => {
        // Your Own Logic
        if (res.selfControlNavType === SelfControlNavType.None) {
          this.renderer.removeClass(
            this.elementRef.nativeElement,
            'self-control-header-shift'
          );
        } else {
          this.renderer.addClass(
            this.elementRef.nativeElement,
            'self-control-header-shift'
          );
        }
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

範例中的 directive 會判斷 NavBar 狀態中的 Type 來決定要回傳的 class name,讓與 NavBar 相依的 DOM 能透過 class name 來處理各自的樣式或邏輯。

用法也很簡單,只要在需要配合 NavBar 改動樣式的 DOM 上直接加上定義好的html tag appSelfControlNavCss,並記得在 ts 檔中匯入 directive,就可以掛上 class name 了。用這個方式最棒的地方是我們節省了 ngClass 還有到處訂閱 NavBar 狀態這兩件事,將邏輯收束。

<div appSelfControlNavCss>
</div>
@Component({
  ...
  imports: [SelfControlNavCssDirective],
  ...
})

到這邊我們的 NavBar 就完成了,下面我們來科普一下什麼是Angular 的自定義 directive


自定義 directive:

簡單來說,Angular 的自定義 directive 就是一段可以用來控制 DOM 的程式碼,可以讓我們創建新的標籤或改變現有元素的行為,並且是可以被複用的。

為什麼要用自定義 directive 呢?

  • 提高可重用性:
    自定義 directive 讓你能夠將常用的功能和邏輯封裝在一起,然後在不同的組件中重複使用,而不需要重複編寫代碼。
  • 增強可維護性:
    將特定功能封裝在指令中,可以讓程式碼結構更清晰,便於日後的維護和更新。當需求改變時,只需要修改指令中的代碼,使用該指令的所有地方都會自動更新。
  • 擴展 HTML 語義:
    自定義 directive 可以讓你創建新的 HTML 標籤或屬性,增加應用的語義性,讓其他開發者更容易理解其用途。例如,可以創建一個 <my-custom-button> 標籤,來表示一個特定的按鈕功能。
  • 封裝複雜邏輯:
    將複雜的 DOM 操作或業務邏輯封裝到指令中,使得 HTML 結構更簡潔。這樣可以減少在模板中直接操作 DOM 的需要,遵循 Angular 的數據綁定和組件化的設計理念。

下一篇文章我們來實作 httpInterceptor


上一篇
# 前端實作案例分享:  透過Store進行頁面緩存
下一篇
# 前端實作案例分享:  HttpInterceptor
系列文
轉生成前端工程師後,30步離開新手村!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言